/*
 * FreeRTOS+TCP <DEVELOPMENT BRANCH>
 * Copyright (C) 2022 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * SPDX-License-Identifier: MIT
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * http://aws.amazon.com/freertos
 * http://www.FreeRTOS.org
 */

/******************************************************************************
*
* See the following web page for essential buffer allocation scheme usage and
* configuration details:
* https://freertos.org/Documentation/03-Libraries/02-FreeRTOS-plus/02-FreeRTOS-plus-TCP/05-Buffer-management
*
******************************************************************************/

/* Standard includes. */
#include <stdint.h>

/* FreeRTOS includes. */
#include "FreeRTOS.h"
#include "task.h"
#include "queue.h"
#include "semphr.h"

/* FreeRTOS+TCP includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_IP_Private.h"
#include "NetworkInterface.h"
#include "NetworkBufferManagement.h"

/* For an Ethernet interrupt to be able to obtain a network buffer there must
 * be at least this number of buffers available. */
#define baINTERRUPT_BUFFER_GET_THRESHOLD    ( 3 )

/* A list of free (available) NetworkBufferDescriptor_t structures. */
static List_t xFreeBuffersList;

/* Some statistics about the use of buffers. */
static UBaseType_t uxMinimumFreeNetworkBuffers = 0U;

/* Declares the pool of NetworkBufferDescriptor_t structures that are available
 * to the system.  All the network buffers referenced from xFreeBuffersList exist
 * in this array.  The array is not accessed directly except during initialisation,
 * when the xFreeBuffersList is filled (as all the buffers are free when the system
 * is booted). */
static NetworkBufferDescriptor_t xNetworkBuffers[ ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS ];

/* This constant is defined as true to let FreeRTOS_TCP_IP.c know that the
 * network buffers have constant size, large enough to hold the biggest Ethernet
 * packet. No resizing will be done. */
const BaseType_t xBufferAllocFixedSize = pdTRUE;

/* The semaphore used to obtain network buffers. */
static SemaphoreHandle_t xNetworkBufferSemaphore = NULL;

#if ( ipconfigTCP_IP_SANITY != 0 )
    static char cIsLow = pdFALSE;
    UBaseType_t bIsValidNetworkDescriptor( const NetworkBufferDescriptor_t * pxDesc );
#else
    static UBaseType_t bIsValidNetworkDescriptor( const NetworkBufferDescriptor_t * pxDesc );
#endif /* ipconfigTCP_IP_SANITY */

static void prvShowWarnings( void );

/* The user can define their own ipconfigBUFFER_ALLOC_LOCK() and
 * ipconfigBUFFER_ALLOC_UNLOCK() macros, especially for use form an ISR.  If these
 * are not defined then default them to call the normal enter/exit critical
 * section macros. */
#if !defined( ipconfigBUFFER_ALLOC_LOCK )

    #define ipconfigBUFFER_ALLOC_INIT()    do {} while( ipFALSE_BOOL )
    #define ipconfigBUFFER_ALLOC_LOCK_FROM_ISR()                                            \
    UBaseType_t uxSavedInterruptStatus = ( UBaseType_t ) portSET_INTERRUPT_MASK_FROM_ISR(); \
    {
    #define ipconfigBUFFER_ALLOC_UNLOCK_FROM_ISR()               \
    portCLEAR_INTERRUPT_MASK_FROM_ISR( uxSavedInterruptStatus ); \
}

    #define ipconfigBUFFER_ALLOC_LOCK()      taskENTER_CRITICAL()
    #define ipconfigBUFFER_ALLOC_UNLOCK()    taskEXIT_CRITICAL()

#endif /* ipconfigBUFFER_ALLOC_LOCK */

/*-----------------------------------------------------------*/

#if ( ipconfigTCP_IP_SANITY != 0 )

/* HT: SANITY code will be removed as soon as the library is stable
 * and and ready to become public
 * Function below gives information about the use of buffers */
    #define WARN_LOW     ( 2 )
    #define WARN_HIGH    ( ( 5 * ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS ) / 10 )

#endif /* ipconfigTCP_IP_SANITY */

/*-----------------------------------------------------------*/

#if ( ipconfigTCP_IP_SANITY != 0 )

    BaseType_t prvIsFreeBuffer( const NetworkBufferDescriptor_t * pxDescr )
    {
        return ( bIsValidNetworkDescriptor( pxDescr ) != 0 ) &&
               ( listIS_CONTAINED_WITHIN( &xFreeBuffersList, &( pxDescr->xBufferListItem ) ) != 0 );
    }
    /*-----------------------------------------------------------*/

    static void prvShowWarnings( void )
    {
        UBaseType_t uxCount = uxGetNumberOfFreeNetworkBuffers();

        if( ( ( cIsLow == 0 ) && ( uxCount <= WARN_LOW ) ) || ( ( cIsLow != 0 ) && ( uxCount >= WARN_HIGH ) ) )
        {
            cIsLow = !cIsLow;
            FreeRTOS_debug_printf( ( "*** Warning *** %s %lu buffers left\n", cIsLow ? "only" : "now", uxCount ) );
        }
    }
    /*-----------------------------------------------------------*/

    UBaseType_t bIsValidNetworkDescriptor( const NetworkBufferDescriptor_t * pxDesc )
    {
        uint32_t offset = ( uint32_t ) ( ( ( const char * ) pxDesc ) - ( ( const char * ) xNetworkBuffers ) );

        if( ( offset >= sizeof( xNetworkBuffers ) ) ||
            ( ( offset % sizeof( xNetworkBuffers[ 0 ] ) ) != 0 ) )
        {
            return pdFALSE;
        }

        return ( UBaseType_t ) ( pxDesc - xNetworkBuffers ) + 1;
    }
    /*-----------------------------------------------------------*/

#else /* if ( ipconfigTCP_IP_SANITY != 0 ) */
    static UBaseType_t bIsValidNetworkDescriptor( const NetworkBufferDescriptor_t * pxDesc )
    {
        ( void ) pxDesc;
        return ( UBaseType_t ) pdTRUE;
    }
    /*-----------------------------------------------------------*/

    static void prvShowWarnings( void )
    {
    }
    /*-----------------------------------------------------------*/

#endif /* ipconfigTCP_IP_SANITY */

BaseType_t xNetworkBuffersInitialise( void )
{
    BaseType_t xReturn;
    uint32_t x;

    /* Only initialise the buffers and their associated kernel objects if they
     * have not been initialised before. */
    if( xNetworkBufferSemaphore == NULL )
    {
        /* In case alternative locking is used, the mutexes can be initialised
         * here */
        ipconfigBUFFER_ALLOC_INIT();

        #if ( configSUPPORT_STATIC_ALLOCATION == 1 )
        {
            static StaticSemaphore_t xNetworkBufferSemaphoreBuffer;
            xNetworkBufferSemaphore = xSemaphoreCreateCountingStatic(
                ( UBaseType_t ) ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS,
                ( UBaseType_t ) ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS,
                &xNetworkBufferSemaphoreBuffer );
        }
        #else
        {
            xNetworkBufferSemaphore = xSemaphoreCreateCounting( ( UBaseType_t ) ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS, ( UBaseType_t ) ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS );
        }
        #endif /* configSUPPORT_STATIC_ALLOCATION */

        configASSERT( xNetworkBufferSemaphore != NULL );

        if( xNetworkBufferSemaphore != NULL )
        {
            vListInitialise( &xFreeBuffersList );

            /* Initialise all the network buffers.  The buffer storage comes
             * from the network interface, and different hardware has different
             * requirements. */
            vNetworkInterfaceAllocateRAMToBuffers( xNetworkBuffers );

            for( x = 0U; x < ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS; x++ )
            {
                /* Initialise and set the owner of the buffer list items. */
                vListInitialiseItem( &( xNetworkBuffers[ x ].xBufferListItem ) );
                listSET_LIST_ITEM_OWNER( &( xNetworkBuffers[ x ].xBufferListItem ), &xNetworkBuffers[ x ] );

                /* Currently, all buffers are available for use. */
                vListInsert( &xFreeBuffersList, &( xNetworkBuffers[ x ].xBufferListItem ) );
            }

            uxMinimumFreeNetworkBuffers = ( UBaseType_t ) ipconfigNUM_NETWORK_BUFFER_DESCRIPTORS;
        }
    }

    if( xNetworkBufferSemaphore == NULL )
    {
        xReturn = pdFAIL;
    }
    else
    {
        xReturn = pdPASS;
    }

    return xReturn;
}
/*-----------------------------------------------------------*/

NetworkBufferDescriptor_t * pxGetNetworkBufferWithDescriptor( size_t xRequestedSizeBytes,
                                                              TickType_t xBlockTimeTicks )
{
    NetworkBufferDescriptor_t * pxReturn = NULL;
    BaseType_t xInvalid = pdFALSE;
    UBaseType_t uxCount;

    /* The current implementation only has a single size memory block, so
     * the requested size parameter is not used (yet). */
    ( void ) xRequestedSizeBytes;

    if( xNetworkBufferSemaphore != NULL )
    {
        /* If there is a semaphore available, there is a network buffer
         * available. */
        if( xSemaphoreTake( xNetworkBufferSemaphore, xBlockTimeTicks ) == pdPASS )
        {
            /* Protect the structure as it is accessed from tasks and
             * interrupts. */
            ipconfigBUFFER_ALLOC_LOCK();
            {
                pxReturn = ( NetworkBufferDescriptor_t * ) listGET_OWNER_OF_HEAD_ENTRY( &xFreeBuffersList );

                if( ( bIsValidNetworkDescriptor( pxReturn ) != pdFALSE_UNSIGNED ) &&
                    listIS_CONTAINED_WITHIN( &xFreeBuffersList, &( pxReturn->xBufferListItem ) ) )
                {
                    ( void ) uxListRemove( &( pxReturn->xBufferListItem ) );
                }
                else
                {
                    xInvalid = pdTRUE;
                }
            }
            ipconfigBUFFER_ALLOC_UNLOCK();

            if( xInvalid == pdTRUE )
            {
                /* _RB_ Can printf() be called from an interrupt?  (comment
                 * above says this can be called from an interrupt too) */

                /* _HT_ The function shall not be called from an ISR. Comment
                 * was indeed misleading. Hopefully clear now?
                 * So the printf()is OK here. */
                FreeRTOS_debug_printf( ( "pxGetNetworkBufferWithDescriptor: INVALID BUFFER: %p (valid %lu)\n",
                                         pxReturn, bIsValidNetworkDescriptor( pxReturn ) ) );
                pxReturn = NULL;
            }
            else
            {
                /* Reading UBaseType_t, no critical section needed. */
                uxCount = listCURRENT_LIST_LENGTH( &xFreeBuffersList );

                /* For stats, latch the lowest number of network buffers since
                 * booting. */
                if( uxMinimumFreeNetworkBuffers > uxCount )
                {
                    uxMinimumFreeNetworkBuffers = uxCount;
                }

                pxReturn->xDataLength = xRequestedSizeBytes;
                pxReturn->pxInterface = NULL;
                pxReturn->pxEndPoint = NULL;

                #if ( ipconfigTCP_IP_SANITY != 0 )
                {
                    prvShowWarnings();
                }
                #endif /* ipconfigTCP_IP_SANITY */

                #if ( ipconfigUSE_LINKED_RX_MESSAGES != 0 )
                {
                    /* make sure the buffer is not linked */
                    pxReturn->pxNextBuffer = NULL;
                }
                #endif /* ipconfigUSE_LINKED_RX_MESSAGES */
            }

            iptraceNETWORK_BUFFER_OBTAINED( pxReturn );
        }
        else
        {
            /* lint wants to see at least a comment. */
            iptraceFAILED_TO_OBTAIN_NETWORK_BUFFER();
        }
    }

    return pxReturn;
}
/*-----------------------------------------------------------*/

NetworkBufferDescriptor_t * pxNetworkBufferGetFromISR( size_t xRequestedSizeBytes )
{
    NetworkBufferDescriptor_t * pxReturn = NULL;

    /* The current implementation only has a single size memory block, so
     * the requested size parameter is not used (yet). */
    ( void ) xRequestedSizeBytes;

    /* If there is a semaphore available then there is a buffer available, but,
     * as this is called from an interrupt, only take a buffer if there are at
     * least baINTERRUPT_BUFFER_GET_THRESHOLD buffers remaining.  This prevents,
     * to a certain degree at least, a rapidly executing interrupt exhausting
     * buffer and in so doing preventing tasks from continuing. */
    if( uxQueueMessagesWaitingFromISR( ( QueueHandle_t ) xNetworkBufferSemaphore ) > ( UBaseType_t ) baINTERRUPT_BUFFER_GET_THRESHOLD )
    {
        if( xSemaphoreTakeFromISR( xNetworkBufferSemaphore, NULL ) == pdPASS )
        {
            /* Protect the structure as it is accessed from tasks and interrupts. */
            ipconfigBUFFER_ALLOC_LOCK_FROM_ISR();
            {
                pxReturn = ( NetworkBufferDescriptor_t * ) listGET_OWNER_OF_HEAD_ENTRY( &xFreeBuffersList );
                uxListRemove( &( pxReturn->xBufferListItem ) );
            }
            ipconfigBUFFER_ALLOC_UNLOCK_FROM_ISR();

            iptraceNETWORK_BUFFER_OBTAINED_FROM_ISR( pxReturn );
        }
    }

    if( pxReturn == NULL )
    {
        iptraceFAILED_TO_OBTAIN_NETWORK_BUFFER_FROM_ISR();
    }

    return pxReturn;
}
/*-----------------------------------------------------------*/

BaseType_t vNetworkBufferReleaseFromISR( NetworkBufferDescriptor_t * const pxNetworkBuffer )
{
    BaseType_t xHigherPriorityTaskWoken = pdFALSE;

    /* Ensure the buffer is returned to the list of free buffers before the
     * counting semaphore is 'given' to say a buffer is available. */
    ipconfigBUFFER_ALLOC_LOCK_FROM_ISR();
    {
        vListInsertEnd( &xFreeBuffersList, &( pxNetworkBuffer->xBufferListItem ) );
    }
    ipconfigBUFFER_ALLOC_UNLOCK_FROM_ISR();

    ( void ) xSemaphoreGiveFromISR( xNetworkBufferSemaphore, &xHigherPriorityTaskWoken );
    iptraceNETWORK_BUFFER_RELEASED( pxNetworkBuffer );

    return xHigherPriorityTaskWoken;
}
/*-----------------------------------------------------------*/

void vReleaseNetworkBufferAndDescriptor( NetworkBufferDescriptor_t * const pxNetworkBuffer )
{
    BaseType_t xListItemAlreadyInFreeList;

    if( bIsValidNetworkDescriptor( pxNetworkBuffer ) == pdFALSE_UNSIGNED )
    {
        FreeRTOS_debug_printf( ( "vReleaseNetworkBufferAndDescriptor: Invalid buffer %p\n", pxNetworkBuffer ) );
    }
    else
    {
        /* Ensure the buffer is returned to the list of free buffers before the
         * counting semaphore is 'given' to say a buffer is available. */
        ipconfigBUFFER_ALLOC_LOCK();
        {
            {
                xListItemAlreadyInFreeList = listIS_CONTAINED_WITHIN( &xFreeBuffersList, &( pxNetworkBuffer->xBufferListItem ) );

                if( xListItemAlreadyInFreeList == pdFALSE )
                {
                    vListInsertEnd( &xFreeBuffersList, &( pxNetworkBuffer->xBufferListItem ) );
                }
            }
        }
        ipconfigBUFFER_ALLOC_UNLOCK();

        if( xListItemAlreadyInFreeList )
        {
            FreeRTOS_debug_printf( ( "vReleaseNetworkBufferAndDescriptor: %p ALREADY RELEASED (now %lu)\n",
                                     pxNetworkBuffer, uxGetNumberOfFreeNetworkBuffers() ) );
        }
        else
        {
            ( void ) xSemaphoreGive( xNetworkBufferSemaphore );
            prvShowWarnings();
        }

        iptraceNETWORK_BUFFER_RELEASED( pxNetworkBuffer );
    }
}
/*-----------------------------------------------------------*/

UBaseType_t uxGetMinimumFreeNetworkBuffers( void )
{
    return uxMinimumFreeNetworkBuffers;
}
/*-----------------------------------------------------------*/

UBaseType_t uxGetNumberOfFreeNetworkBuffers( void )
{
    return listCURRENT_LIST_LENGTH( &xFreeBuffersList );
}

NetworkBufferDescriptor_t * pxResizeNetworkBufferWithDescriptor( NetworkBufferDescriptor_t * pxNetworkBuffer,
                                                                 size_t xNewSizeBytes )
{
    /* In BufferAllocation_1.c all network buffer are allocated with a
     * maximum size of 'ipTOTAL_ETHERNET_FRAME_SIZE'.No need to resize the
     * network buffer. */
    pxNetworkBuffer->xDataLength = xNewSizeBytes;
    return pxNetworkBuffer;
}

/*#endif */ /* ipconfigINCLUDE_TEST_CODE */
